﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Psm.Messaging;
using Psm.Transport;
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using Psm.Logging;
using Psm.Container;


namespace Psm.Protocol.FrameProtocol
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export("BUFFERED_FRAME_PROTOCOL", typeof(IProtocol))]
    class BufferedFrameProtocol : IProtocol
    {
        private static ILogger logger = Repository.GetInstance<ILogger>();
        private IClientTransport clientTransport;
        private IMessageFactory messageFactory;

        private int lengthRead = 0;
        private byte[] lengthBuffer = new byte[4];
        private int messageRead = 0;
        private byte[] messageBuffer;

        public void InitializeProtocol(IClientTransport clientTransport, IMessageFactory messageFactory)
        {
            this.clientTransport = clientTransport;
            this.messageFactory = messageFactory;
        }

        public void SendMessage(IMessage message)
        {
            try
            {
                byte[] msgBuffer = message.Serialize();
                byte[] sendBuffer = new byte[msgBuffer.Length + 4];
                byte[] lengthBuffer = BitConverter.GetBytes(msgBuffer.Length);
                lengthBuffer.CopyTo(sendBuffer, 0);
                msgBuffer.CopyTo(sendBuffer, 4); //copying can be avoided by providing the buffer to serialize
                clientTransport.SendAsync(sendBuffer);
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }

        public event NewMessageDelegate NewMessage;

        public void StartProcessing()
        {
            try
            {
                clientTransport.BeginReceive(this.lengthBuffer, lengthRead, lengthBuffer.Length - lengthRead, new ReceiveCallback(this.ReceiveLength));
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }

        public void ReceiveLength(IClientTransport transport)
        {
            try
            {
                lengthRead += clientTransport.EndReceive();
                if (lengthRead == 4)
                {
                    messageBuffer = new byte[BitConverter.ToInt32(lengthBuffer, 0)];
                    messageRead = 0;
                    clientTransport.BeginReceive(messageBuffer, messageRead, messageBuffer.Length - messageRead, new ReceiveCallback(this.ReceiveMessage));
                }
                else
                {
                    clientTransport.BeginReceive(this.lengthBuffer, lengthRead, lengthBuffer.Length - lengthRead, new ReceiveCallback(this.ReceiveLength));
                }
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }

        public void ReceiveMessage(IClientTransport transport)
        {
            try
            {
                messageRead += clientTransport.EndReceive();
                if (messageRead == messageBuffer.Length)
                {
                    lengthRead = 0;
                    IMessage message = messageFactory.CreateMessage(messageBuffer);
                    OnNewMessage(message);
                    clientTransport.BeginReceive(lengthBuffer, lengthRead, lengthBuffer.Length - lengthRead, new ReceiveCallback(this.ReceiveLength));
                }
                else
                {
                    clientTransport.BeginReceive(messageBuffer, messageRead, messageBuffer.Length - messageRead, new ReceiveCallback(this.ReceiveMessage));
                }
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }

        private void OnNewMessage(IMessage message)
        {
            if (this.NewMessage != null)
            {
                NewMessage(this, new NewMessageEventArgs(message));
            }
        }
    }
}
